using System;
using System.Collections.Generic;
using System.Threading;
using System.Reflection;
using System.Reflection.Emit;
using System.Diagnostics;

namespace Dida
{
	public class TraitsAssembly
	{
		AssemblyBuilder asmBuilder;
		ModuleBuilder moduleBuilder;
		
		int nextName = 0;
		object nameLock;

		public TraitsAssembly()
		{
			nameLock = 0;

			AppDomain domain = AppDomain.CurrentDomain;
			asmBuilder = domain.DefineDynamicAssembly(
				new System.Reflection.AssemblyName("TraitsAssembly"),
				AssemblyBuilderAccess.Run);

			moduleBuilder = asmBuilder.DefineDynamicModule("TraitsModule");
		}

		internal AssemblyBuilder AsmBuilder
		{
			get { return asmBuilder; }
		}

		internal ModuleBuilder ModuleBuilder
		{
			get { return moduleBuilder; }
		}

		internal string FindFreeClassName(string baseName)
		{
			string curName = baseName;
			int nextIndex = 1;
			while (asmBuilder.GetType(curName) != null)
			{
				curName = baseName + "_" + nextIndex.ToString();
				nextIndex++;
			}

			return curName;
		}

		internal string GenerateName()
		{
			lock (nameLock)
			{
				string ret = "_" + nextName.ToString();
				nextName++;
				return ret;
			}
		}

		internal static MethodBuilder CreateCompatableMethod(TypeBuilder type,
			MethodInfo src)
		{
			ParameterInfo[] srcParams = src.GetParameters();
			Type[] paramTypes = new Type[srcParams.Length];
			Type[][] paramRequiredModifiers = new Type[srcParams.Length][];
			Type[][] paramOptionalModifiers = new Type[srcParams.Length][];
			for (int i = 0; i < srcParams.Length; i++)
			{
				paramTypes[i] = srcParams[i].ParameterType;
			}

			// mask out the abstract attribute.
			MethodAttributes attrs = src.Attributes;
			attrs &= ~MethodAttributes.Abstract;

			return type.DefineMethod(src.Name, attrs,
				CallingConventions.HasThis, src.ReturnType,
				src.ReturnParameter.GetRequiredCustomModifiers(),
				src.ReturnParameter.GetOptionalCustomModifiers(),
				paramTypes, paramRequiredModifiers, paramOptionalModifiers);
		}

		static TraitsAssembly defInstance = null;
		public static TraitsAssembly Instance
		{
			get
			{
				if (defInstance == null)
					defInstance = new TraitsAssembly();
				return defInstance;
			}
		}

		static TraitsAssembly dynamicTraitsAsm = null;
		public static TraitsAssembly DynamicTraitsAssembly
		{
			get
			{
				if (dynamicTraitsAsm == null)
					dynamicTraitsAsm = new TraitsAssembly();
				return dynamicTraitsAsm;
			}
		}
	}

	internal class TraitClassCodeGen
	{
		private string name;
		
		private TypeBuilder mainClass;
		private ConstructorBuilder mainClassCtor;
		private ILGenerator mainClassCtorGen;

		private MethodBuilder getNTraitsMethod;
		private ILGenerator getNTraitsGen;

		private MethodBuilder getTraitByIndexMethod;
		private ILGenerator getTraitByIndexGen;

		private FieldBuilder traitsField;
		private List<TraitCodeGen> traits = new List<TraitCodeGen>();

		private Dictionary<string, FieldBuilder> classPropertyFields =
			new Dictionary<string,FieldBuilder>();

		public TraitClassCodeGen(string baseName,int nTraits)
		{
			name = TraitsAssembly.Instance.FindFreeClassName(baseName);
			
			CreateMainClass(nTraits);
		}

		private void CreateMainClass(int nTraits)
		{
			mainClass = TraitsAssembly.Instance.ModuleBuilder.DefineType(TraitsAssembly.Instance.GenerateName(),
				TypeAttributes.NotPublic, typeof(TraitInstance));

			// create the ctor
			mainClassCtor = mainClass.DefineConstructor(MethodAttributes.Public,
				CallingConventions.Standard, new Type[0]);
			mainClassCtorGen = mainClassCtor.GetILGenerator();

			// create the traits field
			traitsField = mainClass.DefineField("traits", typeof(Array), FieldAttributes.Public);
			// initialization code in the ctor
			mainClassCtorGen.Emit(OpCodes.Ldarg_0);
			mainClassCtorGen.Emit(OpCodes.Ldc_I4, nTraits);
			mainClassCtorGen.Emit(OpCodes.Newarr,typeof(Trait));
			mainClassCtorGen.Emit(OpCodes.Stfld, traitsField);
			
			CreateGetNTraits();
			CreateGetTraitByIndex();
		}

		private void CreateGetNTraits()
		{
			MethodInfo getNTraitsAbstract = typeof(TraitInstance).GetMethod(
				"GetNTraits", Type.EmptyTypes);
			getNTraitsMethod = TraitsAssembly.CreateCompatableMethod(mainClass, getNTraitsAbstract);
			mainClass.DefineMethodOverride(getNTraitsMethod, getNTraitsAbstract);
			getNTraitsGen = getNTraitsMethod.GetILGenerator();

			getNTraitsGen.Emit(OpCodes.Ldarg_0);
			getNTraitsGen.Emit(OpCodes.Ldfld, traitsField);
			getNTraitsGen.Emit(OpCodes.Ldlen);
			getNTraitsGen.Emit(OpCodes.Ret);
		}

		private void CreateGetTraitByIndex()
		{
			MethodInfo getTraitAbstract = typeof(TraitInstance).GetMethod(
				"GetTrait", new Type[] { typeof(int) });
			getTraitByIndexMethod = TraitsAssembly.CreateCompatableMethod(mainClass, getTraitAbstract);
			mainClass.DefineMethodOverride(getTraitByIndexMethod, getTraitAbstract);
			getTraitByIndexGen = getTraitByIndexMethod.GetILGenerator();

			getTraitByIndexGen.Emit(OpCodes.Ldarg_0);
			getTraitByIndexGen.Emit(OpCodes.Ldfld, traitsField);
			getTraitByIndexGen.Emit(OpCodes.Ldarg_1);
			getTraitByIndexGen.Emit(OpCodes.Ldelem_Ref);
			getTraitByIndexGen.Emit(OpCodes.Ret);
		}

		public void Finish()
		{
			mainClassCtorGen.Emit(OpCodes.Ret);

			foreach (TraitCodeGen trait in traits)
				trait.Finish();
		}

		public Type MainType
		{
			get
			{
				return mainClass.CreateType();
			}
		}

		public void AddTrait(TraitCodeGen trait)
		{
			// add initialization code to the constructor
			int index = traits.Count;
			mainClassCtorGen.Emit(OpCodes.Ldarg_0);
			mainClassCtorGen.Emit(OpCodes.Ldfld,traitsField);

			mainClassCtorGen.Emit(OpCodes.Ldc_I4,(Int32)index);

			mainClassCtorGen.Emit(OpCodes.Ldarg_0);		// the first arg of the trait ctor
			mainClassCtorGen.Emit(OpCodes.Newobj,trait.Ctor);
			
			mainClassCtorGen.Emit(OpCodes.Stelem_Ref);
			
			traits.Add(trait);
		}

		public FieldBuilder RequestPropertyField(string name,Type type)
		{
			string fieldName = "property_" + name;
			FieldBuilder field;
			if (classPropertyFields.TryGetValue(fieldName, out field))
				return field;
			else
			{
				field = mainClass.DefineField(name,type,FieldAttributes.Assembly);
				classPropertyFields.Add(fieldName, field);
				return field;
			}
		}

		public TraitCodeGen GetTrait(int i)
		{
			return traits[i];
		}

		public int NTraits
		{
			get { return traits.Count; }
		}

		public Type IntermediateType
		{
			get { return mainClass; }
		}

		public string Name
		{
			get { return name; }
		}

		public FieldInfo TraitsArrayField
		{
			get { return traitsField; }
		}

		public int GetTraitIndex(Type type)
		{
			for (int i = 0; i < traits.Count; i++)
			{
				if (traits[i].BaseTrait == type)
				{
					return i;
				}
			}

			Debug.Fail("Trait not found");
			return -1;
		}
	}

	internal class TraitCodeGen
	{
		TraitClassCodeGen traitClass;

		// the trait (the one the user made, using abstract methods and all)
		// that we are implementing now.
		private Type baseTrait;

		// the type we are making, which implements the abstract methods
		// in the trait class the user made.
		private TypeBuilder type;
		private ConstructorBuilder ctor;
		private ILGenerator ctorGen;
		private FieldBuilder traitClassField;

		private List<TraitDeclarationCodeGen> declMethods = new List<TraitDeclarationCodeGen>();

		public TraitCodeGen(TraitClassCodeGen traitClass,Type trait)
		{
			this.traitClass = traitClass;
			baseTrait = trait;

			string name = TraitsAssembly.Instance.FindFreeClassName(traitClass.Name + "_" + trait.Name);
			type = TraitsAssembly.Instance.ModuleBuilder.DefineType(name,TypeAttributes.Public,baseTrait);

			traitClassField = type.DefineField("traitClass",
				traitClass.IntermediateType,FieldAttributes.Private);
			
			// create the ctor
			ctor = type.DefineConstructor(MethodAttributes.Public,
				CallingConventions.Standard,
				new Type[] { traitClass.IntermediateType });
			ctorGen = ctor.GetILGenerator();

			// call the base class ctor
			ConstructorInfo baseCTor = baseTrait.GetConstructor(new Type[0]);
			Type baseType = baseTrait.BaseType;
			while (baseCTor == null && baseType != null)
			{
				baseCTor = baseType.GetConstructor(new Type[0]);
				baseType = baseType.BaseType;
			}
			if(baseCTor != null)
			{
				ctorGen.Emit(OpCodes.Ldarg_0);
				ctorGen.Emit(OpCodes.Call, baseCTor);
			}

			ctorGen.Emit(OpCodes.Ldarg_0);
			ctorGen.Emit(OpCodes.Ldarg_1);			
			ctorGen.Emit(OpCodes.Stfld, traitClassField);
			ctorGen.Emit(OpCodes.Ret);
		}

		public TraitDeclarationCodeGen AddDeclaration(MethodInfo baseMethod)
		{
			Debug.Assert(baseMethod.IsAbstract || baseMethod.IsVirtual);

			TraitDeclarationCodeGen decl = new TraitDeclarationCodeGen(
				this,type, baseMethod);
			declMethods.Add(decl);
			return decl;
		}

		public void AddTraitClassProperty(TraitProperty prop)
		{
			PropertyInfo propInfo = prop.PropertyInfo;
			FieldBuilder propertyField = traitClass.RequestPropertyField(
				prop.PropertyInfo.Name,	prop.PropertyInfo.ReflectedType);

			PropertyBuilder propBuilder = type.DefineProperty(
				propInfo.Name, propInfo.Attributes, propInfo.PropertyType, null);

			MethodBuilder getMethod = TraitsAssembly.CreateCompatableMethod(
				type, propInfo.GetGetMethod());
			type.DefineMethodOverride(getMethod, propInfo.GetGetMethod());

			ILGenerator getMethodGen = getMethod.GetILGenerator();
			getMethodGen.Emit(OpCodes.Ldarg_0);
			getMethodGen.Emit(OpCodes.Ldfld, propertyField);
			getMethodGen.Emit(OpCodes.Ret);

			propBuilder.SetGetMethod(getMethod);
		}

		public void AddTraitLocalProperty(TraitProperty prop)
		{
			PropertyInfo propInfo = prop.PropertyInfo;
			
			FieldBuilder propertyField = type.DefineField(
				"property_" + propInfo.Name,propInfo.PropertyType,FieldAttributes.Private);

			PropertyBuilder propBuilder = type.DefineProperty(
				propInfo.Name, propInfo.Attributes, propInfo.PropertyType, null);

			MethodBuilder getMethod = TraitsAssembly.CreateCompatableMethod(
				type, propInfo.GetGetMethod());
			type.DefineMethodOverride(getMethod, propInfo.GetGetMethod());

			ILGenerator getMethodGen = getMethod.GetILGenerator();
			getMethodGen.Emit(OpCodes.Ldarg_0);
			getMethodGen.Emit(OpCodes.Ldfld, propertyField);
			getMethodGen.Emit(OpCodes.Ret);

			propBuilder.SetGetMethod(getMethod);
		}

		public void Finish()
		{
			foreach (TraitDeclarationCodeGen decl in declMethods)
			{
				decl.Finish();
			}
			
			type.CreateType();
		}

		public Type BaseTrait
		{
			get { return baseTrait; }
		}

		public ConstructorInfo Ctor
		{
			get { return ctor; }
		}

		public FieldBuilder TraitClassField
		{
			get { return traitClassField; }
		}

		public TraitClassCodeGen TraitClass
		{
			get { return traitClass; }
		}
	}

	internal class TraitDeclarationCodeGen
	{
		private TraitCodeGen traitCodeGen;
		private MethodInfo baseMethod;
		private MethodBuilder builder;
		private ILGenerator codeGen;
		private LocalBuilder retLocal;

		public TraitDeclarationCodeGen(TraitCodeGen traitCodeGen,
			TypeBuilder type,MethodInfo baseMethod)
		{
			this.traitCodeGen = traitCodeGen;
			this.baseMethod = baseMethod;

			builder = TraitsAssembly.CreateCompatableMethod(type, baseMethod);
			type.DefineMethodOverride(builder, baseMethod);
			codeGen = builder.GetILGenerator();

			// if it's has a return value, create a local that will hold it
			if (baseMethod.ReturnType != typeof(void))
				retLocal = codeGen.DeclareLocal(baseMethod.ReturnType);
		}

		public void Finish()
		{
			// if it's has a return value, get it from the local
			if (baseMethod.ReturnType != typeof(void))
				codeGen.Emit(OpCodes.Ldloc,retLocal);
			codeGen.Emit(OpCodes.Ret);
		}

		public void AddImplementation(Type type, MethodInfo method)
		{
			// load the class that defines the implementation
			codeGen.Emit(OpCodes.Ldarg_0);
			codeGen.Emit(OpCodes.Ldfld, traitCodeGen.TraitClassField);
			codeGen.Emit(OpCodes.Ldfld, traitCodeGen.TraitClass.TraitsArrayField);
			codeGen.Emit(OpCodes.Ldc_I4,traitCodeGen.TraitClass.GetTraitIndex(type));
			codeGen.Emit(OpCodes.Ldelem_Ref);

			int nParams = method.GetParameters().Length;
			for(int i = 0;i < nParams;i++)
			{
				codeGen.Emit(OpCodes.Ldarg, i + 1);
			}

			codeGen.EmitCall(OpCodes.Call, method, Type.EmptyTypes);

			// if it's has a return value, store it in the local
			if (method.ReturnType != typeof(void))
				codeGen.Emit(OpCodes.Stloc,retLocal);
		}
	}
}